﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;


namespace team33_ass3
{
    class Map
    {
        public int MAP_HEIGHT;
        public int MAP_WIDTH;
        private MapBlock[,] map;
        public List<MapBlock> walls;
        public List<MapBlock> walkables;

        public Map()
        {
            
        }

        public Map(int width, int height)
        {
            this.MAP_HEIGHT = height;
            this.MAP_WIDTH = width;
            this.map = new MapBlock[width, height];
            initMap();
        }

        public void add(int x, int y, MapBlock block)
        {
            map[x, y] = block;
        }

        public void initMap()
        {
            for (int i = 0; i < MAP_WIDTH; i++)
            {
                for (int j = 0; j < MAP_HEIGHT; j++)
                {
                    map[i, j] = new MapBlock(i, j, 90, true);
                }
            }
        }

        /// <summary>
        /// Hunt and Kill Algorithm in play. Auto Level Generation.
        /// </summary>
        public void huntAndKill()
        {
            // Visited Blocks.
            List<MapBlock> visited = new List<MapBlock>();
            // pick a random place
            Random rand = new Random();
            int X = rand.Next(MAP_WIDTH - 1);
            int Y = rand.Next(MAP_HEIGHT - 1);
            // keeping randoming the next one, until we can't move.

            // Make the borders
            for (int i = 0; i < MAP_WIDTH; i++)
            {
                for (int j = 0; j < MAP_HEIGHT; j++)
                {
                    if (i == 0 || i == MAP_WIDTH - 1 ||
                        j == 0 || j == MAP_HEIGHT - 1)
                    {
                        visited.Add(map[i, j]);
                    }
                }
            }
            
            do
            {
                // walk around until dead end
                MapBlock mapBlock = map[X, Y];
                walk(ref visited, mapBlock);

                // can we hunt anymore ?
            } while (hunt(ref visited, out X, out Y));

            updateMap();
        }

        /// <summary>
        /// This method should only be used for debugging. It outputs the Map into
        /// a text file.
        /// </summary>
        /// <param name="fileName">File to be written to</param>
        public void mapTextFileOutput(String fileName)
        {
            using (TextWriter tw = File.CreateText(fileName))
            {
                for (int i = 0; i < MAP_WIDTH; i++)
                {
                    for (int j = 0; j < MAP_HEIGHT; j++)
                    {
                        if (map[i, j].isSolid())
                            tw.Write("X");
                        else tw.Write(" ");
                    }
                    tw.WriteLine();
                }
            }
        }

        /// <summary>
        /// On it's current location it randomly chooses a direction, then it would
        /// walk as much as it can until it reaches a dead-end
        /// </summary>
        /// <param name="visited"></param>
        /// <param name="current"></param>
        private void walk(ref List<MapBlock> visited, MapBlock current)
        {
            int currentX = current.getX();
            int currentY = current.getY();

             // check boundaries
            if (!((currentX - 1) < 0 || (currentX + 1) > (MAP_WIDTH - 1) ||
                (currentY - 1) < 0 || (currentY + 1) > (MAP_HEIGHT - 1)))
            {

                MapBlock north = map[currentX, currentY - 1];
                MapBlock east = map[currentX + 1, currentY];
                MapBlock south = map[currentX, currentY + 1];
                MapBlock west = map[currentX - 1, currentY];

                Random rnd = new Random();
                // Randomize this list please
                List<MapBlock> tmp = new List<MapBlock>();
                tmp.Add(north);
                tmp.Add(east);
                tmp.Add(south);
                tmp.Add(west);

                tmp = Randomize(tmp);

                // Add this path to visited, and make the block walkable
                current.setSolid(false);
                visited.Add(current);

                foreach (MapBlock mp in tmp)
                {
                    if (!visited.Contains(mp))
                    {
                        makeWalls(ref visited, current, mp);
                        walk(ref visited, mp);
                    }
                }

            }
        }

        /// <summary>
        /// This adds the walls to the visited list, except for the destination
        /// </summary>
        /// <param name="current"></param>
        /// <param name="destination"></param>
        /// <param name="visited"></param>
        private void makeWalls(ref List<MapBlock> visited, MapBlock current, MapBlock destination)
        {
            MapBlock north = map[current.getX(), current.getY() - 1];
            MapBlock east = map[current.getX() + 1, current.getY()];
            MapBlock south = map[current.getX(), current.getY() + 1];
            MapBlock west = map[current.getX() - 1, current.getY()];

            List<MapBlock> tmp = new List<MapBlock>();
            tmp.Add(north);
            tmp.Add(east);
            tmp.Add(south);
            tmp.Add(west);

            foreach (MapBlock m in tmp)
            {
                if (!m.Equals(destination))
                {
                    if (!visited.Contains(m))
                        visited.Add(m);
                }
            }
        }

        /// <summary>
        /// This algorithm goes row-by-row looking for the first unvisited MapBlock that is adjacent to
        /// two MapBlock (one that is a wall (solid) and one that is walkable (not solid)
        /// </summary>
        /// <param name="visited"></param>
        /// <param name="x"></param>
        /// <param name="y"></param>
        /// <returns></returns>
        private bool hunt(ref List<MapBlock> visited, out int x, out int y)
        {
            for (int i = 0; i < MAP_WIDTH; i++)
            {
                for (int j = 0; j < MAP_HEIGHT; j++)
                {
                    MapBlock tmp = map[i, j];
                    if (!visited.Contains(tmp))
                    {
                        // check boundaries
                        if (!((i - 2) < 0 || (i + 2) > (MAP_WIDTH  - 1) ||
                            (j - 2) < 0 || (j + 2) > (MAP_HEIGHT - 1)))
                        {
                            MapBlock north = map[tmp.getX(), tmp.getY() - 1];
                            MapBlock east = map[tmp.getX() + 1, tmp.getY()];
                            MapBlock south = map[tmp.getX(), tmp.getY() + 1];
                            MapBlock west = map[tmp.getX() - 1, tmp.getY()];

                            List<MapBlock> directions = new List<MapBlock>();
                            directions.Add(north);
                            directions.Add(east);
                            directions.Add(south);
                            directions.Add(west);

                            directions = Randomize(directions);

                            foreach (MapBlock mp in directions)
                            {
                                if (visited.Contains(mp) && checkMapBlock(mp))
                                {
                                    mp.setSolid(false); // make it walkable the wall
                                    x = tmp.getX();
                                    y = tmp.getY();

                                    return true;
                                }
                            }
                        }
                        
                    }
                }
            }

            x = 0;
            y = 0;
            return false;
        }

        private bool checkMapBlock(MapBlock checker)
        {
            MapBlock north = map[checker.getX(), checker.getY() - 1];
            MapBlock east = map[checker.getX() + 1, checker.getY()];
            MapBlock south = map[checker.getX(), checker.getY() + 1];
            MapBlock west = map[checker.getX() - 1, checker.getY()];

            if (checker.canWalkTo(north) || checker.canWalkTo(east) ||
                checker.canWalkTo(south) || checker.canWalkTo(west))
                return true;

            return false;
        }

        public List<MapBlock> getWalkablesFrom(MapBlock center)
        {
            List<MapBlock> adajcentBlocks = new List<MapBlock>();

            foreach (MapBlock mp in walkables)
            {
                if (center.canWalkTo(mp))
                    adajcentBlocks.Add(mp);
            }

            return adajcentBlocks;
        }

        public void updateMap()
        {
            walls = new List<MapBlock>();
            walkables = new List<MapBlock>();
            for (int i = 0; i < MAP_WIDTH; i++)
            {
                for (int j = 0; j < MAP_HEIGHT; j++)
                {
                    MapBlock mp = map[i, j];
                    if (mp.isSolid())
                        walls.Add(mp);
                    else
                        walkables.Add(mp);
                }
            }
        }

        /// <summary>
        /// From a position, return a Block
        /// </summary>
        /// <param name="position"></param>
        /// <returns></returns>
        public MapBlock getBlock(Vector3 position)
        {
            foreach (MapBlock mp in walkables)
            {
                if (mp.intersects(position))
                    return mp;
            }

            return new MapBlock(0, 0);
        }

        public MapBlock[,] getMap()
        {
            return map;
        }

        public static List<T> Randomize<T>(List<T> list)
        {
            List<T> randomizedList = new List<T>();
            Random rnd = new Random();
            while (list.Count > 0)
            {
                int index = rnd.Next(0, list.Count); //pick a random item from the master list
                randomizedList.Add(list[index]); //place it at the end of the randomized list
                list.RemoveAt(index);
            }
            return randomizedList;
        }
    }
}
